#!/usr/bin/env python3
#-*- coding:utf-8 -*-
from __future__ import annotations

from configparser import ConfigParser, ExtendedInterpolation
from json import dumps
from logging import getLogger, DEBUG
from os import environ
from os.path import abspath, dirname, join, splitext
from ssl import create_default_context
from sys import argv
from typing import Dict, Self

from pika.adapters.blocking_connection import BlockingChannel, BlockingConnection
from pika.connection import ConnectionParameters, Parameters, SSLOptions
from pika.credentials import PlainCredentials
from pika.delivery_mode import DeliveryMode
from pika.spec import BasicProperties

class RabbitMQ(object):

	host: str = Parameters.DEFAULT_HOST
	address: str = Parameters.DEFAULT_HOST
	port: int = Parameters.DEFAULT_PORT
	ssl: bool = Parameters.DEFAULT_SSL
	virtual_host: str = Parameters.DEFAULT_VIRTUAL_HOST
	username: str = None
	password: str = None

	connection: BlockingConnection = None
	channel: BlockingChannel = None

	def __init__(self, name, **kwargs):
		self.logger = getLogger(__name__)

		config = ConfigParser(interpolation=ExtendedInterpolation(), empty_lines_in_values=False)
		configfiles = (
			splitext(__file__)[0] + '.cfg',
			join(dirname(argv[0]), 'rabbitmq.cfg'),
			join(environ['HOME'], 'rabbitmq.cfg')
		)
		for configFile in configfiles:
			self.logger.debug('Reading config from %s', abspath(configFile))
		config.read(configfiles)

		if not config.has_section(name):
			raise ValueError('No Configuration Found for ' + name)

		args = {}
		args.update(config[name])
		if kwargs:
			args.update(kwargs)

		if self.logger.isEnabledFor(DEBUG):
			for key, value in args.items():
				self.logger.debug(
					'%s config: %s=%s(%s)',
					name,
					key,
					'********' if key == 'password' else value,
					type(value)
				)

		if args.get('host'):
			self.host = args.get('host')

		if args.get('address'):
			self.address = args.get('address')
		elif args.get('host'):
			self.address = args.get('host')

		if args.get('port'):
			self.port = int(args.get('port'))

		if args.get('ssl') and args.get('ssl').lower() == 'true':
			ssl = args.get('ssl')
			if isinstance(ssl, str):
				ssl = ssl.lower() == 'true'
			if ssl:
				self.ssl = True
				if not args.get('port'):
					self.port = Parameters.DEFAULT_SSL_PORT

		if args.get('virtual_host'):
			self.virtual_host = args.get('virtual_host')

		if args.get('username'):
			self.username = args.get('username')

		if args.get('password'):
			self.password = args.get('password')

	def get_connection(self) -> BlockingConnection:
		if self.connection:
			return self.connection

		params: ConnectionParameters = ConnectionParameters(
			host = self.address
		)

		if self.username or self.password:
			params.credentials = PlainCredentials(self.username, self.password)

		if self.ssl:
			ssl_context = create_default_context()
			ssl_options = SSLOptions(ssl_context, self.host)
			params.ssl_options = ssl_options

		if self.port > 0:
			params.port = self.port

		if self.virtual_host:
			params.virtual_host = self.virtual_host

		self.connection = BlockingConnection(parameters=params)
		return self.connection

	def get_channel(self) -> BlockingChannel:
		if self.channel:
			return self.channel

		connection: BlockingConnection = self.get_connection()
		self.channel = connection.channel()
		return self.channel

	def send(self, message: Dict[str, str | int | bool | float], exchange: str = '', routing_key: str = '') -> None:
		text: str = dumps(message)
		self.logger.info('Sending to [%s/%s] %s', exchange, routing_key, text)

		channel: BlockingChannel = self.get_channel()
		channel.basic_publish(
			exchange = exchange,
			routing_key = routing_key,
			body = text,
			properties = BasicProperties(
				content_type = 'application/json',
				content_encoding = 'utf-8',
				delivery_mode = DeliveryMode.Persistent
			)
		)

	def __enter__(self) -> Self:
		return self

	def __exit__(self, exc_type, exc_value, traceback):
		if self.channel:
			self.channel.close()
		if self.connection:
			self.connection.close()
		self.channel = None
		self.connection = None
